package com.mymarkdown.app.fx;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.mymarkdown.app.fx.assemblys.FxTreeItem;
import com.mymarkdown.app.fx.assemblys.GlobalMenu;
import com.mymarkdown.app.fx.assemblys.TextFieldTreeCellImpl;
import com.mymarkdown.app.fx.enums.FxEventEnum;
import com.mymarkdown.app.fx.event.hander.KeyWordHandler;
import com.mymarkdown.app.fx.event.model.FxEvent;
import com.mymarkdown.app.interfaces.ModifyWindow;
import com.mymarkdown.app.model.RightTreeInfo;
import com.mymarkdown.app.websocket.enums.MessageTypeEnum;
import com.mymarkdown.app.websocket.message.MessagePojo;
import com.mymarkdown.app.common.constant.Constant;
import com.mymarkdown.app.fx.event.hander.MenuHandler;
import com.mymarkdown.app.model.UserLatelyInfo;
import com.mymarkdown.app.util.CurrentUserUtil;
import javafx.application.Application;
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.input.*;
import javafx.scene.layout.*;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javafx.util.Callback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author zenglun
 * @date 2021/12/3 11:16 下午
 * @description: TODO
 */
@Slf4j
public class FxApplication extends Application {

	private static ConfigurableApplicationContext springBootApplication;

	private final static String DEFAULT_URL_NAME = "fx.app.url";

	private final static String DEFAULT_WIDTH_NAME = "fx.app.width";

	private final static String DEFAULT_HEIGHT_NAME = "fx.app.height";

	private final static String DEFAULT_TITLE_NAME = "fx.app.title";

	private final static String DEFAULT_MENUS_FILE = Constant.AllFilePath.BUILD_ALL_MENUS_FILE;

	private static Stage primaryStage;

	private static WebView webView;

	private static WebEngine engine;

	private static int width;

	private static int height;

	private static String title;

	private static String url;

	private static MessagePojo latelyOperationMdBodyMessage = new MessagePojo();

	private static TreeView treeView;

	static {
		initSystem();
	}

	private static void initSystem() {
		// 此处进行配置的初始化工作
		// 创建文件
		try {
			mkdirConfig();
		} catch (IOException e) {
			e.printStackTrace();
			log.error("[FxApplication]---[run],创建配置文件失败!");
			System.exit(-1);
		}

	}

	/**
	 * 键盘监听处理事件分发器
	 */
	private static KeyWordHandler keyWordHandler;

	public static TreeView getTreeView() {
		return treeView;
	}

	public static ConfigurableApplicationContext getSpringBootApplication() {
		return springBootApplication;
	}

	public static WebEngine getEngine() {
		return engine;
	}

	public static void run(Class<?> clazz, String... args) {

		springBootApplication = SpringApplication.run(clazz, args);
		ConfigurableEnvironment environment = springBootApplication.getEnvironment();

		// 获取相关的配置
		url = environment.getProperty(DEFAULT_URL_NAME);
		width = Integer.parseInt(Objects.requireNonNull(environment.getProperty(DEFAULT_HEIGHT_NAME)));
		height = Integer.parseInt(Objects.requireNonNull(environment.getProperty(DEFAULT_WIDTH_NAME)));
		title = environment.getProperty(DEFAULT_TITLE_NAME);

		if (StrUtil.isEmpty(url)) {
			throw new NullPointerException("webView的url不能为空!");
		}

		if (StrUtil.isEmpty(title)) {
			throw new NullPointerException("应用程序的标题为空!");
		}

		// 加载历史数据
		try {
			String body = IoUtil.read(new FileInputStream(Constant.AllFilePath.USER_LATELY_INFO_FILE),
					StandardCharsets.UTF_8);
			log.info("[FxApplication]---[run],读取到的历史操作记录为:{}", body);
			// 读取历史数据失败!
			if (StrUtil.isNotBlank(body)) {
				UserLatelyInfo userLatelyInfo = JSON.parseObject(body, UserLatelyInfo.class);
				// 设置当前数据
				CurrentUserUtil.getSelf().setUserLatelyInfo(userLatelyInfo);
			}
			// 在通过当前操作的用户数据读取数据
			String latelyOperationFile = CurrentUserUtil.getSelf().getUserLatelyInfo().getLatelyOperationFile();

			// 如果读取到的最近操作文件为空,则不进行
			if (StrUtil.isNotBlank(latelyOperationFile)) {
				String latelyOperationMdBody = IoUtil.read(new FileInputStream(latelyOperationFile),
						StandardCharsets.UTF_8);

				// 读取完之后下发通过给用户
				latelyOperationMdBodyMessage.setMessageType(MessageTypeEnum.LOAD_FILE.getMessageType());
				latelyOperationMdBodyMessage.setBody(latelyOperationMdBody);
			}

		} catch (FileNotFoundException e) {
			e.printStackTrace();
			log.error("[FxApplication]---[run],加载用户历史操作数据失败,异常信息为:{}", e);
			latelyOperationMdBodyMessage.setMessageType(MessageTypeEnum.TISP_MSG.getMessageType());
			latelyOperationMdBodyMessage.setBody("加载历史操作数据失败!");
		}

		launch(args);
	}

	private static void mkdirConfig() throws IOException {
		File file = new File(Constant.AllFilePath.BUILD_ALL_MENUS_FILE);
		if (!file.exists()) {
			file.createNewFile();
		}
		file = new File(Constant.AllFilePath.USER_LATELY_INFO_FILE);
		if (!file.exists()) {
			file.createNewFile();
		}
		file = new File(Constant.AllFilePath.NOT_SAVE_MD_BODY_FILE);
		if (!file.exists()) {
			file.createNewFile();
		}
	}

	public static WebView getWebView() {
		return webView;
	}

	public static Stage getPrimaryStage() {
		return primaryStage;
	}

	public static void appendTitle(String str){
		if (StrUtil.isBlank(str)){
			return;
		}
		getPrimaryStage().setTitle(title + str);
	}

	@Override
	public void start(Stage primaryStage) throws Exception {
		FxApplication.primaryStage = primaryStage;
		webView = new WebView();
		webView.setContextMenuEnabled(false);
		engine = webView.getEngine();
		engine.load(url);
		engine.setJavaScriptEnabled(true);
		BorderPane root = new BorderPane();
		MenuBar menuBar = buildMenus();
		root.setTop(menuBar);
		root.setCenter(webView);



		TreeView<String> historyTree = buildRightTree();
		root.setRight(historyTree);
		root.setOnMouseClicked(event -> {
			MouseButton button = event.getButton();
			if (MouseButton.SECONDARY.equals(button)){
				Node intersectedNode = event.getPickResult().getIntersectedNode();
				GlobalMenu.getInstance().show(intersectedNode,  event.getScreenX(), event.getScreenY());
			}
			if (MouseButton.PRIMARY.equals(button)){
				GlobalMenu.getInstance().hide();
			}
		});

		//加载历史操作数据
		ModifyWindow modifyWindow = springBootApplication.getBean(ModifyWindow.class);
		modifyWindow.loadHistoryOperationTree();

		Scene scene = new Scene(root, width, height);
		keyWordHandler = springBootApplication.getBean(KeyWordHandler.class);

		if (Objects.isNull(keyWordHandler)) {
			log.error("[FxApplication]---[start],未找到相关处理bean,异常信息为:{}", new NullPointerException("没有找到处理键盘事件,加载对象失败!"));
			System.exit(-1);
		}

		// 监听组合键
		// 获取剪切板的内容
		// 首先需要判断用户是否有当前操作的文件如果没有，则弹出保存，然后在调用事件，如何有
		KeyCombination ctrlI = new KeyCodeCombination(KeyCode.I, KeyCombination.SHORTCUT_DOWN);
		scene.getAccelerators().put(ctrlI,
				() -> keyWordHandler.handler(engine, new FxEvent().setEventType(FxEventEnum.INSET_IMAGE)));

		KeyCombination ctrlS = new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN);
		scene.getAccelerators().put(ctrlS,
				() -> keyWordHandler.handler(engine, new FxEvent().setEventType(FxEventEnum.SAVE_MD)));

		// 新建快捷键
		KeyCombination ctrlN = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN);
		scene.getAccelerators().put(ctrlN,
				() -> keyWordHandler.handler(engine, new FxEvent().setEventType(FxEventEnum.NEW_CREATE_MD)));

		// 新建快捷键
		KeyCombination ctrlP = new KeyCodeCombination(KeyCode.P, KeyCombination.SHORTCUT_DOWN);
		scene.getAccelerators().put(ctrlP,
				() -> keyWordHandler.handler(engine, new FxEvent().setEventType(FxEventEnum.EXPORT_MD_PDF)));

		KeyCombination ctrlH = new KeyCodeCombination(KeyCode.H, KeyCombination.SHORTCUT_DOWN);
		scene.getAccelerators().put(ctrlH,
				() -> keyWordHandler.handler(engine, new FxEvent().setEventType(FxEventEnum.EXPORT_MD_HTML)));

		KeyCombination ctrlO = new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN);
		scene.getAccelerators().put(ctrlO,
				() -> keyWordHandler.handler(engine, new FxEvent().setEventType(FxEventEnum.OPEN_MD_FILE)));

		KeyCombination ctrlW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN);
		scene.getAccelerators().put(ctrlW,
				() -> keyWordHandler.handler(engine, new FxEvent().setEventType(FxEventEnum.SETTING_CURRENT_WORK_DIR)));
		// 设置图标
		primaryStage.setScene(scene);
		primaryStage.setTitle(title + Constant.NOT_FILE_SHOW_TEXT);

		webView.prefHeightProperty().bind(root.heightProperty());
		webView.prefWidthProperty().bind(root.widthProperty());
		primaryStage.setResizable(true);
		// 关闭时同时调用tomcat退出程序
		try {
			primaryStage.getIcons().add(new Image(new FileInputStream(Constant.AllFilePath.APP_ICON_PATH)));
		} catch (FileNotFoundException e) {
			log.warn("[FxApplication]---[start],在指定目录下未找到图标:{}", Constant.AllFilePath.APP_ICON_PATH);
			try {
				primaryStage.getIcons().add(
						new Image(new FileInputStream(FxApplication.class.getResource("/").getPath() + "logo.png")));
			} catch (FileNotFoundException e1) {
			}
		}
		primaryStage.show();
		primaryStage.setOnCloseRequest(event -> {
			// 关闭之前写入记录到文件中
			try {
				UserLatelyInfo userLatelyInfo = CurrentUserUtil.getSelf().getUserLatelyInfo();
				IoUtil.write(new FileOutputStream(Constant.AllFilePath.USER_LATELY_INFO_FILE), true,
						JSON.toJSONString(userLatelyInfo).getBytes(StandardCharsets.UTF_8));
			} catch (FileNotFoundException e) {
				e.printStackTrace();
				log.error("[FxApplication]---[start],写入历史操作数据失败,异常信息为:{}", e);
			}
			springBootApplication.close();
			// 直接调用退出程序
			System.exit(1);
		});

	}



	private TreeView<String> buildRightTree() {
		TreeItem<String> rootItem = FxTreeItem.getRootItem("文件视图列表");


		rootItem.setExpanded(true);

		//读取超出文件数据并构建数
		String rightTreeJson = null;
		try {
			rightTreeJson = IoUtil.read(new FileInputStream(Constant.AllFilePath.RIGHT_TREE_INFO_JSON_FILE), StandardCharsets.UTF_8);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			log.error("[FxApplication]---[buildRightTree],配置文件未找到!");
			System.exit(-1);
		}

		if (StrUtil.isEmpty(rightTreeJson)){
			throw new NullPointerException("读取到构建右侧树的配置文件信息为空!");
		}
		rootItem.getChildren().addAll(FxTreeItem.getHistoricalOperationItem("操作历史列表"), FxTreeItem.getCurrentOperationFileListItem("当前工作空间列表"));
		treeView = new TreeView<>(rootItem);
		treeView.setEditable(false);
		treeView.setCellFactory(param -> new TextFieldTreeCellImpl());
		return treeView;
	}

	public static MessagePojo getLatelyOperationMdBodyMessage() {
		return latelyOperationMdBodyMessage;
	}

	/**
	 * 从配置文件中读取数据,根据配置的JSON数据进行创建菜单栏{@link com.mymarkdown.app.common.constant.Constant.AllFilePath#BUILD_ALL_MENUS_FILE}
	 * 如果读取到的数据为空，则采用默认的菜单栏,base64加密后的JSON数据
	 * 
	 * @return
	 * @throws FileNotFoundException 当没有找到配置文件时，抛出该异常信息
	 */
	private MenuBar buildMenus() throws FileNotFoundException {
		// 读取配置文件
		MenuBar menuBar = new MenuBar();
		menuBar.setBackground(Background.EMPTY);
		InputStream resourceAsStream = new FileInputStream(DEFAULT_MENUS_FILE);
		String jsonStr = IoUtil.read(resourceAsStream, StandardCharsets.UTF_8);
		log.info("[FxApplication]---[buildMenus],读取的菜单列表JSON为:{}", jsonStr);
		if (StrUtil.isEmpty(jsonStr)) {
			jsonStr = new String(
					Base64.getDecoder().decode(Constant.DEFAULT_MENUS_JSON_BASE64.getBytes(StandardCharsets.UTF_8)));
			// 写入文件
			IoUtil.write(new FileOutputStream(DEFAULT_MENUS_FILE), true, jsonStr.getBytes(StandardCharsets.UTF_8));
		}
		Optional.ofNullable(resourceAsStream).ifPresent(in -> {
			try {
				in.close();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		});
		List list = JSON.parseArray(jsonStr);
		if (CollectionUtil.isEmpty(list)) {
			throw new NullPointerException("菜单列表为空！");
		}

		List<Menu> menus = (List<Menu>) list.stream().map(e -> {
			Map el = (Map) e;
			String title = MapUtil.getStr(el, "title");
			Menu item = new Menu(title);
			item.setId(MapUtil.getStr(el, "id"));
			String beanName = MapUtil.getStr(el, "bean");
			if (StrUtil.isNotBlank(beanName)) {
				MenuHandler bean = springBootApplication.getBean(beanName, MenuHandler.class);
				if (Objects.isNull(bean)) {
					throw new NullPointerException("为找到相关处理bean!");
				}
				item.addEventHandler(EventType.ROOT, event -> bean.handler(engine, new FxEvent()));
			}
			List<Map> menuList = (List<Map>) el.get("items");
			List<MenuItem> menuItems = Optional.ofNullable(menuList).orElseGet(ArrayList::new).stream().map(mItem -> {
				String t = (String) mItem.get("title");
				String id = mItem.get("id").toString();
				MenuItem menuItem = new MenuItem(t);
				menuItem.setId(id);
				String bName = MapUtil.getStr(mItem, "bean");
				if (StrUtil.isNotBlank(bName)) {
					MenuHandler bean = springBootApplication.getBean(bName, MenuHandler.class);
					if (Objects.isNull(bean)) {
						throw new NullPointerException("未找到相关处理bean!");
					}
					menuItem.addEventHandler(EventType.ROOT, et -> bean.handler(engine, new FxEvent()));
				}
				return menuItem;
			}).collect(Collectors.toList());
			item.getItems().addAll(menuItems);
			return item;
		}).collect(Collectors.toList());
		menuBar.getMenus().addAll(menus);
		return menuBar;
	}

}
